#ifndef BTL_IMAGE_PIXELTYPES
#define BTL_IMAGE_PIXELTYPES

#include <boost/static_assert.hpp>
#include <cassert>

namespace btl
{
namespace image
{

template <class PixelT>
struct PixelTraits
{
   typedef PixelT channel_type;
   static const int NUM_CHANNELS = 1;

   static channel_type& get(int channelIdx, PixelT& p);
   static const channel_type& get(int channelIdx, const PixelT& p);
};

template <int NumChannels, typename ChannelType>
class MultiChannelPixel
{
   public:
      MultiChannelPixel(void);
      explicit MultiChannelPixel(const ChannelType& value);

      template <int N, typename T>
      friend bool operator==(const MultiChannelPixel<N, T>& a, const MultiChannelPixel<N, T>& b);
      template <int N, typename T>
      friend bool operator!=(const MultiChannelPixel<N, T>& a, const MultiChannelPixel<N, T>& b);

   protected:
      template <class PixelT> friend struct PixelTraits;
      ChannelType _values[NumChannels];
};

template <int NumChannels, typename ChannelType>
bool operator==(const MultiChannelPixel<NumChannels, ChannelType>& a, const MultiChannelPixel<NumChannels, ChannelType>& b);

struct PixelRGB : public MultiChannelPixel<3, unsigned char>
{
   PixelRGB();
   PixelRGB(unsigned char r, unsigned char g, unsigned char b);
   explicit PixelRGB(unsigned char value);

   unsigned char& red();
   unsigned char& green();
   unsigned char& blue();

   const unsigned char& red() const;
   const unsigned char& green() const;
   const unsigned char& blue() const;
};
BOOST_STATIC_ASSERT(sizeof(PixelRGB) == 3);

typedef unsigned char PixelGrey;
typedef float PixelFloat;

template <int NumChannels, typename ChannelType>
struct PixelTraits< MultiChannelPixel<NumChannels, ChannelType> >
{
   typedef ChannelType channel_type;
   static const int NUM_CHANNELS = NumChannels;

   static channel_type& get(int channelIdx, MultiChannelPixel<NumChannels, ChannelType>& p);
   static const channel_type& get(int channelIdx, const MultiChannelPixel<NumChannels, ChannelType>& p);
};

template <>
struct PixelTraits<PixelRGB> : public PixelTraits< MultiChannelPixel<3, unsigned char> >
   {};

} // namespace image
} // namespace btl

namespace btl
{
using image::PixelRGB;
using image::PixelGrey;
using image::PixelFloat;
using image::PixelTraits;
}

// ====================================================================
// === Implementation

namespace btl
{
namespace image
{

template <typename PixelT>
typename PixelTraits<PixelT>::channel_type& PixelTraits<PixelT>::get(int channelIdx, PixelT& p)
{
   assert(channelIdx == 0);
   return p;
}

template <typename PixelT>
const typename PixelTraits<PixelT>::channel_type& PixelTraits<PixelT>::get(int channelIdx, const PixelT& p)
{
   assert(channelIdx == 0);
   return p;
}

template <int NumChannels, typename ChannelType>
typename PixelTraits<MultiChannelPixel<NumChannels, ChannelType> >::channel_type&
PixelTraits<MultiChannelPixel<NumChannels, ChannelType> >::get(int channelIdx, MultiChannelPixel<NumChannels, ChannelType>& p)
{
   return p._values[channelIdx];
}

template <int NumChannels, typename ChannelType>
const typename PixelTraits<MultiChannelPixel<NumChannels, ChannelType> >::channel_type&
PixelTraits<MultiChannelPixel<NumChannels, ChannelType> >::get(int channelIdx, const MultiChannelPixel<NumChannels, ChannelType>& p)
{
   return p._values[channelIdx];
}

template <int NumChannels, typename ChannelType>
inline MultiChannelPixel<NumChannels, ChannelType>::MultiChannelPixel(void) {}

template <int NumChannels, typename ChannelType>
inline MultiChannelPixel<NumChannels, ChannelType>::MultiChannelPixel(const ChannelType& value)
{
   for(int k = 0; k < NumChannels; ++k)
      _values[k] = value;
}

inline PixelRGB::PixelRGB() {}

inline PixelRGB::PixelRGB(unsigned char r, unsigned char g, unsigned char b)
{
   _values[0] = r;
   _values[1] = g;
   _values[2] = b;
}

inline PixelRGB::PixelRGB(unsigned char value):
   MultiChannelPixel<3, unsigned char>(value) {}

inline unsigned char& PixelRGB::red()
{
   return _values[0];
}

inline unsigned char& PixelRGB::green()
{
   return _values[1];
}

inline unsigned char& PixelRGB::blue()
{
   return _values[2];
}

inline const unsigned char& PixelRGB::red() const
{
   return _values[0];
}

inline const unsigned char& PixelRGB::green() const
{
   return _values[1];
}

inline const unsigned char& PixelRGB::blue() const
{
   return _values[2];
}

template <int NumChannels, typename ChannelType>
inline bool operator==(const MultiChannelPixel<NumChannels, ChannelType>& a, const MultiChannelPixel<NumChannels, ChannelType>& b)
{
   for(int k = 0; k < NumChannels; ++k)
      if(a._values[k] != b._values[k]) return false;
   return true;
}

template <int NumChannels, typename ChannelType>
inline bool operator!=(const MultiChannelPixel<NumChannels, ChannelType>& a, const MultiChannelPixel<NumChannels, ChannelType>& b)
{
   return !(a == b);
}

} // namespace image
} // namespace btl

#endif
